﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ScriptGenerator.cs" company="Akzente.IT">
//   (c) 2012 Akzente.IT
// </copyright>
// <summary>
//   Generate documentation script
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AkzenteIT.DatabaseDocumentationTool.DocScriptGenerator
{
    using System.Text;

    using AkzenteIT.DatabaseDocumentationTool.ViewModels;
    using AkzenteIT.DatabaseDocumentationTool.ViewModels.Base;

    using Microsoft.SqlServer.Management.SqlParser.Parser;

    /// <summary>
    /// Generate documentation script
    /// </summary>
    public class ScriptGenerator
    {
        /// <summary>
        /// The template used for generating the database script
        /// </summary>
        private const string DatabaseTemplate =
            "EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N{0}";

        /// <summary>
        /// The template used for generating the table script
        /// </summary>
        private const string TableTemplate =
            "EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N{0}, @level0type=N'SCHEMA',@level0name=N{1}, @level1type=N'TABLE',@level1name=N{2}";

        /// <summary>
        /// The template used for generating the table script
        /// </summary>
        private const string ViewTemplate =
            "EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N{0}, @level0type=N'SCHEMA',@level0name=N{1}, @level1type=N'VIEW',@level1name=N{2}";

        /// <summary>
        /// The template used for generating the column script
        /// </summary>
        private const string ColumnTemplate =
            "EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N{0}, @level0type=N'SCHEMA',@level0name=N{1}, @level1type=N'TABLE',@level1name=N{2}, @level2type=N'COLUMN',@level2name=N{3}";

        /// <summary>
        /// Initializes a new instance of the <see cref="ScriptGenerator"/> class.
        /// </summary>
        public ScriptGenerator()
        {
            this.RecursionDepth = 3;
        }

        /// <summary>
        /// Gets or sets the recursion depth for script generation in the logical database tree (e.g. [Server] => [Database] => Tables => [Table] => [Column])
        /// </summary>
        /// <value>
        /// The recursion depth.
        /// </value>
        public int RecursionDepth { get; set; }

        /// <summary>
        /// Generate a script for the specified node and all children down to the specified recursion depth.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="builder">The StringBuilder which will receive the script.</param>
        public void GenerateScript(NodeViewModel node, StringBuilder builder)
        {
            this.Visit(node, builder, this.RecursionDepth);
        }

        /// <summary>
        /// Generate a script for the specified node and all children down to the specified recursion depth.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="builder">The StringBuilder which will receive the script.</param>
        /// <param name="recursionDepth">The recursion depth.</param>
        private void Visit(NodeViewModel node, StringBuilder builder, int recursionDepth)
        {
            if (recursionDepth <= 0)
            {
                return;
            }

            // We only generate scripts for database objects that have a description
            if (node is IDbObjectViewModel && !string.IsNullOrWhiteSpace(((IDbObjectViewModel)node).Description))
            {
                if (node is DatabaseViewModel)
                {
                    this.VisitDatabase(node as DatabaseViewModel, builder);
                }
                else if (node is ViewViewModel)
                {
                    this.VisitView(node as ViewViewModel, builder);
                }
                else if (node is TableViewModel)
                {
                    this.VisitTable(node as TableViewModel, builder);
                }
                else if (node is ColumnViewModel)
                {
                    this.VisitColumn(node as ColumnViewModel, builder);
                }
            }

            foreach (var child in node.Children)
            {
                this.Visit(child, builder, recursionDepth - 1);
            }
        }

        /// <summary>
        /// Visits the database.
        /// </summary>
        /// <param name="database">The database.</param>
        /// <param name="builder">The builder.</param>
        private void VisitDatabase(DatabaseViewModel database, StringBuilder builder)
        {
            builder.AppendFormat(DatabaseTemplate, this.Escape(database.Description));
            builder.AppendLine();
        }

        /// <summary>
        /// Visits the table.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="builder">The builder.</param>
        private void VisitTable(TableViewModel table, StringBuilder builder)
        {
            builder.AppendFormat(TableTemplate, this.Escape(table.Description), this.Escape(table.Schema), this.Escape(table.Name));
            builder.AppendLine();
        }

        /// <summary>
        /// Visits the view.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="builder">The builder.</param>
        private void VisitView(ViewViewModel view, StringBuilder builder)
        {
            builder.AppendFormat(ViewTemplate, this.Escape(view.Description), this.Escape(view.Schema), this.Escape(view.Name));
            builder.AppendLine();
        }

        /// <summary>
        /// Visits the column.
        /// </summary>
        /// <param name="column">The column.</param>
        /// <param name="builder">The builder.</param>
        private void VisitColumn(ColumnViewModel column, StringBuilder builder)
        {
            var table = column.Parent as TableViewModel;

            if (table == null)
            {
                return;
            }

            builder.AppendFormat(ColumnTemplate, this.Escape(column.Description), this.Escape(table.Schema), this.Escape(table.Name), this.Escape(column.Name));
            builder.AppendLine();
        }

        /// <summary>
        /// Escapes an SQL string
        /// </summary>
        /// <param name="input">
        /// The input.
        /// </param>
        /// <returns>
        /// The escaped string.
        /// </returns>
        private string Escape(string input)
        {
            return EscapeSequence.SingleQuotedEscapeSequence.Escape(input);
        }
    }
}
